Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / androidApp / src / main / kotlin / org / meshtastic / app / map / MapLayersManager.kt

Displaying Raw • Download

androidApp/src/main/kotlin/org/meshtastic/app/map/MapLayersManager.kt fff95f98db169fca3a5422a141273a0e83e1a968 (fff95f98) Text, 14.48 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.app.map

Tff7b72import T7ee787android.app.Application
Tff7b72import T7ee787android.net.Uri
Tff7b72import T7ee787androidx.core.net.toFile
Tff7b72import T7ee787androidx.core.net.toUri
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787io.ktor.client.HttpClient
Tff7b72import T7ee787io.ktor.client.request.get
Tff7b72import T7ee787io.ktor.client.statement.bodyAsChannel
Tff7b72import T7ee787io.ktor.http.isSuccess
Tff7b72import T7ee787io.ktor.utils.io.jvm.javaio.toInputStream
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.SupervisorJob
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.asStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.update
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.coroutines.withContext
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.app.MapFileImportBus
Tff7b72import T7ee787org.meshtastic.core.common.util.nowMillis
Tff7b72import T7ee787org.meshtastic.core.di.CoroutineDispatchers
Tff7b72import T7ee787org.meshtastic.core.repository.MapPrefs
Tff7b72import T7ee787java.io.File
Tff7b72import T7ee787java.io.FileOutputStream
Tff7b72import T7ee787java.io.IOException
Tff7b72import T7ee787java.io.InputStream

T8b949e/**
* Flavor-neutral owner of the imported map-layer list, its internal-storage persistence, and the GeoJSON/KML import
* plumbing. Both the Google (Google Maps data layer) and F-Droid (OSMdroid overlay) flavors observe [mapLayers] and
* render it their own way; this class is the single home for everything that isn't the final overlay draw.
*
* A [Single] rather than per-flavor ViewModel state so the layer set survives ViewModel recreation and both flavors
* share one implementation. Layers persist as files under `filesDir/map_layers`; hidden/network state lives in
* [MapPrefs].
*/
Tf0883e@Single
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tff7b72class T56d364MapLayersManagerTb4b4b4(
Tff7b72private Tff7b72val Te6edf3applicationTb4b4b4: Te6edf3ApplicationTb4b4b4,
Tff7b72private Tff7b72val Te6edf3dispatchersTb4b4b4: Te6edf3CoroutineDispatchersTb4b4b4,
Tff7b72private Tff7b72val Te6edf3httpClientTb4b4b4: Te6edf3HttpClientTb4b4b4,
Tff7b72private Tff7b72val Te6edf3mapPrefsTb4b4b4: Te6edf3MapPrefsTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3scope Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3SupervisorJobTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3dispatchersTb4b4b4.Te6edf3defaultTb4b4b4)

Tff7b72private Tff7b72val Te6edf3_mapLayers Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3MapLayerItemTff7b72>Tff7b72>Tb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3mapLayersTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3MapLayerItemTff7b72>Tff7b72> Tff7b72= Te6edf3_mapLayersTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

Tff7b72init Tb4b4b4{
Te6edf3loadPersistedLayersTb4b4b4(Tb4b4b4)

T8b949e// Import a map file handed to us via an "Open in / Send to Meshtastic" intent (see MapFileImportBus).
Te6edf3scopeTb4b4b4.Te6edf3launch Tb4b4b4{
Te6edf3MapFileImportBusTb4b4b4.Te6edf3pendingTb4b4b4.Te6edf3collect Tb4b4b4{ Te6edf3uri Tff7b72-Tff7b72>
Te6edf3uri Tff7b72?: Tff7b72returnTf0883e@collect
Te6edf3MapFileImportBusTb4b4b4.Te6edf3pendingTb4b4b4.Te6edf3value Tff7b72= Tff7b72null
Te6edf3addMapLayerTb4b4b4(Te6edf3uriTb4b4b4, Te6edf3uriTb4b4b4.Te6edf3getFileNameTb4b4b4(Te6edf3applicationTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffloadPersistedLayersTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3launchTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3layersDir Tff7b72= Te6edf3FileTb4b4b4(Te6edf3applicationTb4b4b4.Te6edf3filesDirTb4b4b4, Te6edf3LAYERS_DIRTb4b4b4)
T8b949e// await* (not .value) so a cold-start load doesn't see the StateFlow's initial empty default.
Tff7b72val Te6edf3hiddenLayerUrls Tff7b72= Te6edf3mapPrefsTb4b4b4.Te6edf3awaitHiddenLayerUrlsTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3loadedItems Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3layersDirTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3layersDirTb4b4b4.Te6edf3isDirectoryTb4b4b4) Tb4b4b4{
Te6edf3layersDirTb4b4b4.Te6edf3listFilesTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Te6edf3file Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3fileTb4b4b4.Te6edf3isFileTb4b4b4) Tff7b72returnTf0883e@mapNotNull Tff7b72null
Te6edf3resolveLayerTypeTb4b4b4(Te6edf3fileTb4b4b4.Te6edf3extensionTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3layerType Tff7b72-Tff7b72>
Tff7b72val Te6edf3uri Tff7b72= Te6edf3UriTb4b4b4.Te6edf3fromFileTb4b4b4(Te6edf3fileTb4b4b4)
Te6edf3MapLayerItemTb4b4b4(
Te6edf3name Tff7b72= Te6edf3displayNameFromFileNameTb4b4b4(Te6edf3fileTb4b4b4.Te6edf3nameWithoutExtensionTb4b4b4)Tb4b4b4,
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3isVisible Tff7b72= Tff7b72!Te6edf3hiddenLayerUrlsTb4b4b4.Te6edf3containsTb4b4b4(Te6edf3uriTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Te6edf3layerType Tff7b72= Te6edf3layerTypeTb4b4b4,
Te6edf3createdAt Tff7b72= Te6edf3fileTb4b4b4.Te6edf3lastModifiedTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72> T79c0ff0 Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3emptyListTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tff7b72val Te6edf3networkItems Tff7b72=
Te6edf3mapPrefsTb4b4b4.Te6edf3awaitNetworkMapLayersTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Te6edf3networkString Tff7b72-Tff7b72>
Tff7b72val Te6edf3parts Tff7b72= Te6edf3networkStringTb4b4b4.Te6edf3splitTb4b4b4(Te6edf3NETWORK_LAYER_DELIMITERTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3partsTb4b4b4.Te6edf3size Tff7b72=Tff7b72= Te6edf3NETWORK_LAYER_FIELDSTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3uri Tff7b72= Te6edf3partsTff7b72[T79c0ff2Tff7b72]Tb4b4b4.Te6edf3toUriTb4b4b4(Tb4b4b4)
Te6edf3MapLayerItemTb4b4b4(
Te6edf3id Tff7b72= Te6edf3partsTff7b72[T79c0ff0Tff7b72]Tb4b4b4,
Te6edf3name Tff7b72= Te6edf3partsTff7b72[T79c0ff1Tff7b72]Tb4b4b4,
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3isVisible Tff7b72= Tff7b72!Te6edf3hiddenLayerUrlsTb4b4b4.Te6edf3containsTb4b4b4(Te6edf3uriTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Te6edf3layerType Tff7b72= Te6edf3LayerTypeTb4b4b4.Te6edf3KMLTb4b4b4,
Te6edf3isNetwork Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Tb4b4b4}

Te6edf3_mapLayersTb4b4b4.Te6edf3value Tff7b72= Te6edf3loadedItems Tff7b72+ Te6edf3networkItems
Tff7b72if Tb4b4b4(Te6edf3_mapLayersTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3iTb4b4b4(Ta5d6ff"Ta5d6ffLoaded Tffd700${Te6edf3_mapLayersTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff persisted map layers.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffError loading persisted map layersTa5d6ff" Tb4b4b4}
Te6edf3_mapLayersTb4b4b4.Te6edf3value Tff7b72= Te6edf3emptyListTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72fun Td2a8ffaddMapLayerTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3UriTb4b4b4, Te6edf3fileNameTb4b4b4: Tffa657String?Tb4b4b4) Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3launch Tb4b4b4{
Tff7b72val Te6edf3layerName Tff7b72= Te6edf3fileNameTff7b72?.Te6edf3substringBeforeLastTb4b4b4(Ta5d6ff'.'Tb4b4b4) Tff7b72?: Ta5d6ff"Ta5d6ffLayer Tffd700${Te6edf3mapLayersTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3size Tff7b72+ T79c0ff1Tffd700}Ta5d6ff"
Tff7b72val Te6edf3extension Tff7b72=
Te6edf3fileNameTff7b72?.Te6edf3substringAfterLastTb4b4b4(Ta5d6ff'.'Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tff7b72?.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
Tff7b72?: Te6edf3applicationTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3getTypeTb4b4b4(Te6edf3uriTb4b4b4)Tff7b72?.Te6edf3splitTb4b4b4(Ta5d6ff'/'Tb4b4b4)Tff7b72?.Te6edf3lastTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3layerType Tff7b72= Te6edf3resolveLayerTypeTb4b4b4(Te6edf3extensionTb4b4b4)
T8b949e// resolveLayerType only matches non-null input, so a non-null type guarantees a non-null extension.
Tff7b72if Tb4b4b4(Te6edf3layerType Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3extension Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3eTb4b4b4(Ta5d6ff"Ta5d6ffUnsupported map layer file type: Tffd700$Te6edf3extensionTa5d6ff"Tb4b4b4)
Tff7b72returnTf0883e@launch
Tb4b4b4}

Tff7b72val Te6edf3localFileUri Tff7b72= Te6edf3copyFileToInternalStorageTb4b4b4(Te6edf3uriTb4b4b4, Te6edf3layerFileNameTb4b4b4(Te6edf3layerNameTb4b4b4, Te6edf3extensionTb4b4b4)Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3localFileUri Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3_mapLayersTb4b4b4.Te6edf3update Tb4b4b4{
Tffa657it Tff7b72+
Te6edf3MapLayerItemTb4b4b4(Te6edf3name Tff7b72= Te6edf3layerNameTb4b4b4, Te6edf3uri Tff7b72= Te6edf3localFileUriTb4b4b4, Te6edf3layerType Tff7b72= Te6edf3layerTypeTb4b4b4, Te6edf3createdAt Tff7b72= Te6edf3nowMillisTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3eTb4b4b4(Ta5d6ff"Ta5d6ffFailed to copy file to internal storage.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Import a GeoJSON string (e.g. handed back by the Site Planner headless bridge) as a visible local overlay,
* reusing the same internal-storage-backed layer plumbing as file imports.
*/
Tff7b72fun Td2a8ffaddGeoJsonLayerTb4b4b4(Te6edf3nameTb4b4b4: Tffa657StringTb4b4b4, Te6edf3geoJsonTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3launch Tb4b4b4{
Tff7b72val Te6edf3displayName Tff7b72= Te6edf3nameTb4b4b4.Te6edf3ifBlank Tb4b4b4{ Ta5d6ff"Ta5d6ffCoverageTa5d6ff" Tb4b4b4}
Tff7b72val Te6edf3uri Tff7b72= Te6edf3writeStringToInternalStorageTb4b4b4(Te6edf3geoJsonTb4b4b4, Te6edf3layerFileNameTb4b4b4(Te6edf3displayNameTb4b4b4, Te6edf3COVERAGE_EXTENSIONTb4b4b4)Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3uri Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3_mapLayersTb4b4b4.Te6edf3update Tb4b4b4{
Tffa657it Tff7b72+
Te6edf3MapLayerItemTb4b4b4(
Te6edf3name Tff7b72= Te6edf3displayNameTb4b4b4,
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3layerType Tff7b72= Te6edf3LayerTypeTb4b4b4.Te6edf3COVERAGETb4b4b4,
Te6edf3createdAt Tff7b72= Te6edf3nowMillisTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3eTb4b4b4(Ta5d6ff"Ta5d6ffFailed to write GeoJSON layer to internal storage.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/** Returns an error message if [name]/[url] are invalid, or null on success. Adds a persisted network layer. */
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4) T8b949e// guard clauses read clearer than nesting for this validation
Tff7b72fun Td2a8ffaddNetworkMapLayerTb4b4b4(Te6edf3nameTb4b4b4: Tffa657StringTb4b4b4, Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String? Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3nameTb4b4b4.Te6edf3isBlankTb4b4b4(Tb4b4b4) Tff7b72|Tff7b72| Te6edf3urlTb4b4b4.Te6edf3isBlankTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Ta5d6ff"Ta5d6ffInvalid name or URL for network layer.Ta5d6ff"
Tff7b72val Te6edf3uri Tff7b72=
Tff7b72try Tb4b4b4{
Te6edf3urlTb4b4b4.Te6edf3toUriTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffSwallowedExceptionTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72return Ta5d6ff"Ta5d6ffInvalid URL.Ta5d6ff"
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3uriTb4b4b4.Te6edf3scheme Tff7b72!Tff7b72= Ta5d6ff"Ta5d6ffhttpTa5d6ff" Tff7b72&Tff7b72& Te6edf3uriTb4b4b4.Te6edf3scheme Tff7b72!Tff7b72= Ta5d6ff"Ta5d6ffhttpsTa5d6ff"Tb4b4b4) Tff7b72return Ta5d6ff"Ta5d6ffURL must be http or https.Ta5d6ff"

Tff7b72val Te6edf3path Tff7b72= Te6edf3uriTb4b4b4.Te6edf3pathTff7b72?.Te6edf3lowercaseTb4b4b4(Tb4b4b4) Tff7b72?: Ta5d6ff"Ta5d6ff"
Tff7b72val Te6edf3layerType Tff7b72= Tff7b72if Tb4b4b4(Te6edf3pathTb4b4b4.Te6edf3endsWithTb4b4b4(Ta5d6ff"Ta5d6ff.geojsonTa5d6ff"Tb4b4b4) Tff7b72|Tff7b72| Te6edf3pathTb4b4b4.Te6edf3endsWithTb4b4b4(Ta5d6ff"Ta5d6ff.jsonTa5d6ff"Tb4b4b4)Tb4b4b4) Te6edf3LayerTypeTb4b4b4.Te6edf3GEOJSON Tff7b72else Te6edf3LayerTypeTb4b4b4.Te6edf3KML

Tff7b72val Te6edf3newItem Tff7b72= Te6edf3MapLayerItemTb4b4b4(Te6edf3name Tff7b72= Te6edf3nameTb4b4b4, Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4, Te6edf3layerType Tff7b72= Te6edf3layerTypeTb4b4b4, Te6edf3isNetwork Tff7b72= Tff7b72trueTb4b4b4)
Te6edf3_mapLayersTb4b4b4.Te6edf3update Tb4b4b4{ Tffa657it Tff7b72+ Te6edf3newItem Tb4b4b4}
Tff7b72val Te6edf3encoded Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3newItemTb4b4b4.Te6edf3idTb4b4b4, Te6edf3newItemTb4b4b4.Te6edf3nameTb4b4b4, Te6edf3newItemTb4b4b4.Te6edf3uriTb4b4b4)Tb4b4b4.Te6edf3joinToStringTb4b4b4(Te6edf3NETWORK_LAYER_DELIMITERTb4b4b4)
Te6edf3mapPrefsTb4b4b4.Te6edf3updateNetworkMapLayers Tb4b4b4{ Tffa657it Tff7b72+ Te6edf3encoded Tb4b4b4}
Tff7b72return Tff7b72null
Tb4b4b4}

Tff7b72fun Td2a8fftoggleLayerVisibilityTb4b4b4(Te6edf3layerIdTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3target Tff7b72= Te6edf3_mapLayersTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3find Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3layerId Tb4b4b4} Tff7b72?: Tff7b72return
Tff7b72val Te6edf3nowVisible Tff7b72= Tff7b72!Te6edf3targetTb4b4b4.Te6edf3isVisible
Te6edf3_mapLayersTb4b4b4.Te6edf3update Tb4b4b4{ Te6edf3layers Tff7b72-Tff7b72> Te6edf3layersTb4b4b4.Te6edf3map Tb4b4b4{ Tff7b72if Tb4b4b4(Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3layerIdTb4b4b4) Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3isVisible Tff7b72= Te6edf3nowVisibleTb4b4b4) Tff7b72else Tffa657it Tb4b4b4} Tb4b4b4}

Tff7b72val Te6edf3uri Tff7b72= Te6edf3targetTb4b4b4.Te6edf3uriTff7b72?.Te6edf3toStringTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72return
Te6edf3mapPrefsTb4b4b4.Te6edf3updateHiddenLayerUrls Tb4b4b4{ Tff7b72if Tb4b4b4(Te6edf3nowVisibleTb4b4b4) Tffa657it Tff7b72- Te6edf3uri Tff7b72else Tffa657it Tff7b72+ Te6edf3uri Tb4b4b4}
Tb4b4b4}

Tff7b72fun Td2a8ffremoveMapLayerTb4b4b4(Te6edf3layerIdTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3launch Tb4b4b4{
Tff7b72val Te6edf3layerToRemove Tff7b72= Te6edf3_mapLayersTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3find Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3layerId Tb4b4b4}
Te6edf3layerToRemoveTff7b72?.Te6edf3uriTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3uri Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3layerToRemoveTb4b4b4.Te6edf3isNetworkTb4b4b4) Tb4b4b4{
Te6edf3mapPrefsTb4b4b4.Te6edf3updateNetworkMapLayers Tb4b4b4{ Te6edf3entries Tff7b72-Tff7b72>
Te6edf3entriesTb4b4b4.Te6edf3filterNot Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3startsWithTb4b4b4(Ta5d6ff"Tffd700$Te6edf3layerIdTffd700$Te6edf3NETWORK_LAYER_DELIMITERTa5d6ff"Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toSetTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3deleteFileFromInternalStorageTb4b4b4(Te6edf3uriTb4b4b4)
Tb4b4b4}
Te6edf3mapPrefsTb4b4b4.Te6edf3updateHiddenLayerUrls Tb4b4b4{ Tffa657it Tff7b72- Te6edf3uriTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Te6edf3_mapLayersTb4b4b4.Te6edf3update Tb4b4b4{ Te6edf3layers Tff7b72-Tff7b72> Te6edf3layersTb4b4b4.Te6edf3filterNot Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3layerId Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/** Bump a layer's [MapLayerItem.refreshToken] so renderers re-read it (used for network-layer refresh). */
Tff7b72fun Td2a8ffrefreshMapLayerTb4b4b4(Te6edf3layerIdTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Te6edf3_mapLayersTb4b4b4.Te6edf3update Tb4b4b4{ Te6edf3layers Tff7b72-Tff7b72>
Te6edf3layersTb4b4b4.Te6edf3map Tb4b4b4{ Tff7b72if Tb4b4b4(Tffa657itTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3layerIdTb4b4b4) Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3refreshToken Tff7b72= Tffa657itTb4b4b4.Te6edf3refreshToken Tff7b72+ T79c0ff1Tb4b4b4) Tff7b72else Tffa657it Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72fun Td2a8ffrefreshAllVisibleNetworkLayersTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3_mapLayersTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNetwork Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3isVisible Tb4b4b4}Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3refreshMapLayerTb4b4b4(Tffa657itTb4b4b4.Te6edf3idTb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffRecycleTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetInputStreamFromUriTb4b4b4(Te6edf3layerItemTb4b4b4: Te6edf3MapLayerItemTb4b4b4)Tb4b4b4: Te6edf3InputStream? Tb4b4b4{
Tff7b72val Te6edf3uriToLoad Tff7b72= Te6edf3layerItemTb4b4b4.Te6edf3uri Tff7b72?: Tff7b72return Tff7b72null
Tff7b72return Te6edf3withContextTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3layerItemTb4b4b4.Te6edf3isNetwork Tff7b72&Tff7b72& Tb4b4b4(Te6edf3uriToLoadTb4b4b4.Te6edf3scheme Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffhttpTa5d6ff" Tff7b72|Tff7b72| Te6edf3uriToLoadTb4b4b4.Te6edf3scheme Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffhttpsTa5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3httpClientTb4b4b4.Te6edf3getTb4b4b4(Te6edf3uriToLoadTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3responseTb4b4b4.Te6edf3statusTb4b4b4.Te6edf3isSuccessTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
T8b949e// Log only host, not the full URL (paths can carry user-identifying info).
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3e Tb4b4b4{
Ta5d6ff"Ta5d6ffHTTP Tffd700${Te6edf3responseTb4b4b4.Te6edf3statusTffd700}Ta5d6ff fetching network layer from Tffd700${Te6edf3uriToLoadTb4b4b4.Te6edf3hostTffd700}Ta5d6ff"
Tb4b4b4}
Tff7b72returnTf0883e@withContext Tff7b72null
Tb4b4b4}
Te6edf3responseTb4b4b4.Te6edf3bodyAsChannelTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toInputStreamTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3applicationTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openInputStreamTb4b4b4(Te6edf3uriToLoadTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
T8b949e// The caller is a composition-scoped effect, so a layer refresh or leaving the map cancels this fetch
T8b949e// mid-flight. That is not a load failure and must not be reported as one.
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
T8b949e// Redact the URI: a content:///file:// path can include a user-chosen file name.
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffError opening InputStream for layer (scheme=Tffd700${Te6edf3uriToLoadTb4b4b4.Te6edf3schemeTffd700}Ta5d6ff)Ta5d6ff" Tb4b4b4}
Tff7b72null
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffcopyFileToInternalStorageTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3UriTb4b4b4, Te6edf3fileNameTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3Uri? Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
T8b949e// openInputStream can return null (documented) — fail rather than return a Uri for a file never
T8b949e// written.
Tff7b72val Te6edf3inputStream Tff7b72= Te6edf3applicationTb4b4b4.Te6edf3contentResolverTb4b4b4.Te6edf3openInputStreamTb4b4b4(Te6edf3uriTb4b4b4) Tff7b72?: Tff7b72returnTf0883e@withContext Tff7b72null
Tff7b72val Te6edf3directory Tff7b72= Te6edf3FileTb4b4b4(Te6edf3applicationTb4b4b4.Te6edf3filesDirTb4b4b4, Te6edf3LAYERS_DIRTb4b4b4)Tb4b4b4.Te6edf3apply Tb4b4b4{ Tff7b72if Tb4b4b4(Tff7b72!Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3mkdirsTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3outputFile Tff7b72= Te6edf3FileTb4b4b4(Te6edf3directoryTb4b4b4, Te6edf3fileNameTb4b4b4)
Te6edf3inputStreamTb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3input Tff7b72-Tff7b72> Te6edf3FileOutputStreamTb4b4b4(Te6edf3outputFileTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3output Tff7b72-Tff7b72> Te6edf3inputTb4b4b4.Te6edf3copyToTb4b4b4(Te6edf3outputTb4b4b4) Tb4b4b4} Tb4b4b4}
Te6edf3UriTb4b4b4.Te6edf3fromFileTb4b4b4(Te6edf3outputFileTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3IOExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffError copying file to internal storageTa5d6ff" Tb4b4b4}
Tff7b72null
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffwriteStringToInternalStorageTb4b4b4(Te6edf3contentTb4b4b4: Tffa657StringTb4b4b4, Te6edf3fileNameTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3Uri? Tff7b72=
Te6edf3withContextTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3directory Tff7b72= Te6edf3FileTb4b4b4(Te6edf3applicationTb4b4b4.Te6edf3filesDirTb4b4b4, Te6edf3LAYERS_DIRTb4b4b4)Tb4b4b4.Te6edf3apply Tb4b4b4{ Tff7b72if Tb4b4b4(Tff7b72!Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3mkdirsTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3outputFile Tff7b72= Te6edf3FileTb4b4b4(Te6edf3directoryTb4b4b4, Te6edf3fileNameTb4b4b4)
Te6edf3outputFileTb4b4b4.Te6edf3writeTextTb4b4b4(Te6edf3contentTb4b4b4)
Te6edf3UriTb4b4b4.Te6edf3fromFileTb4b4b4(Te6edf3outputFileTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3IOExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffError writing GeoJSON to internal storageTa5d6ff" Tb4b4b4}
Tff7b72null
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffdeleteFileFromInternalStorageTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3UriTb4b4b4) Tb4b4b4{
Te6edf3withContextTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3file Tff7b72= Te6edf3uriTb4b4b4.Te6edf3toFileTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3fileTb4b4b4.Te6edf3existsTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3fileTb4b4b4.Te6edf3deleteTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Te6edf3TAGTb4b4b4)Tb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffError deleting file from internal storageTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72const Tff7b72val Te6edf3TAG Tff7b72= Ta5d6ff"Ta5d6ffMapLayersManagerTa5d6ff"
Tff7b72const Tff7b72val Te6edf3LAYERS_DIR Tff7b72= Ta5d6ff"Ta5d6ffmap_layersTa5d6ff"
Tff7b72const Tff7b72val Te6edf3NETWORK_LAYER_DELIMITER Tff7b72= Ta5d6ff"Ta5d6ff|:|Ta5d6ff"
Tff7b72const Tff7b72val Te6edf3NETWORK_LAYER_FIELDS Tff7b72= T79c0ff3 T8b949e// id|:|name|:|uri
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.2s